using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;
using TTDotNet.Util;
using TTDotNet.Engine.Terrain;
using System.Diagnostics;

namespace TTDotNet.Engine.ThreeDEngine
{
    public class GameGrid : AJObject
    {
        private short gridWidth;
        private short gridLength;

        private CustomVertex.PositionTextured[] singleVert = new CustomVertex.PositionTextured[1];
        private int strideSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CustomVertex.PositionTextured));
        private GraphicsStream[,] vertStreams;

        private GameTexture grassTex;

        private GameTexture fullHighlightTex;
        private GameTexture halfHighlightTex;

        public static Rectangle viewableLand;
        private const int border = 30;

        private Matrix[] rotMatrices;

        private CustomVertex.PositionTextured[,][] vertices;

        private VertexBuffer[,] vertexBuffers;

        private RotateType[,] rotateTypes;
        private GameTexture[,] textures;
        private int[,] tileNumbers;

        private int allTilesIndices;
        private int prims;

        private IndexBuffer[,] gridIndexBuffer;
        private int[] sectorIndices;

        private Dictionary<GameTexture, Dictionary<int, List<int>>>[,] indexSpaces;
        private Dictionary<GameTexture, Dictionary<int, List<int>>>[,] tileIndices;

        private Dictionary<GameTexture, Dictionary<int, IndexBuffer>>[,] ibs;
        
        private Dictionary<Tile, int> indexStarts;

        private Dictionary<GameTexture, Dictionary<int, List<int>>>[,] highlightSpaces;
        private Dictionary<GameTexture, Dictionary<int, List<int>>>[,] highlightIndices;
        private Dictionary<GameTexture, Dictionary<int, IndexBuffer>>[,] highlightIBs;
        private Dictionary<Tile, int> highlightStarts;
        private RotateType[,] highlightRotates;
        private HighlightType[,] highlightTypes;

        private Dictionary<HighlightType, GameTexture> highlightToTex;

        private const int splitSize = 16;


        public GameGrid(int width, int length) : base(ObjectType.Landscape)
        {
            if (width % splitSize != 0 || length % splitSize != 0)
            {
                throw new Exception("ZOMGZ mutliple of 16");
            }

            viewableLand = Rectangle.Empty;

            gridWidth = (short)width;
            gridLength = (short)length;

            vertStreams = new GraphicsStream[gridWidth / splitSize, gridLength / splitSize];

            allTilesIndices = splitSize * splitSize * 12;

            tileIndices = new Dictionary<GameTexture, Dictionary<int, List<int>>>[width / splitSize, length / splitSize];

            indexStarts = new Dictionary<Tile, int>();
            indexSpaces = new Dictionary<GameTexture, Dictionary<int, List<int>>>[width / splitSize, length / splitSize] ;

            SetupHighlight();

            string grass = "artwork/512/grassNewC.jpg";
            grassTex = Game.textureCacher.LoadTexture(grass);

            BuildGrid(width, length);
        }

        private void SetupHighlight()
        {
            highlightSpaces = new Dictionary<GameTexture, Dictionary<int, List<int>>>[gridWidth / splitSize, gridLength / splitSize];
            highlightIndices = new Dictionary<GameTexture, Dictionary<int, List<int>>>[gridWidth / splitSize, gridLength / splitSize];
            highlightIBs = new Dictionary<GameTexture, Dictionary<int, IndexBuffer>>[gridWidth / splitSize, gridLength / splitSize];
            highlightStarts = new Dictionary<Tile, int>();
            highlightRotates = new RotateType[gridWidth, gridLength];
            highlightTypes = new HighlightType[gridWidth, gridLength];

            highlightToTex = new Dictionary<HighlightType, GameTexture>();

            string fullPath = "artwork/512/fullHighlight.tga";
            fullHighlightTex = Game.textureCacher.LoadTexture(fullPath);
            string halfPath = "artwork/512/halfHighlight.tga";
            halfHighlightTex = Game.textureCacher.LoadTexture(halfPath);


            highlightToTex[HighlightType.Full] = fullHighlightTex;
            highlightToTex[HighlightType.Half] = halfHighlightTex;
            highlightToTex[HighlightType.NoHighlight] = null;
        }

        private void BuildGrid(int width, int length)
        {
            prims = ((gridWidth * gridLength) * 4) + (4 * (gridLength - 1));

            BuildFourTriangleVB();
            BuildIndices();
            BuildTextures();
            BuildMatrices();
        }

        private void BuildFourTriangleVB()
        {
            vertices = new CustomVertex.PositionTextured[gridWidth / splitSize, gridLength / splitSize][];

            int vertsPerSector = ((splitSize + 1) * (splitSize + 1)) + (splitSize * splitSize);
            int num = 0;

            for (int zs = 0; zs < gridLength / splitSize; zs++)
            {
                for (int xs = 0; xs < gridWidth / splitSize; xs++)
                {
                    vertices[xs, zs] = new CustomVertex.PositionTextured[vertsPerSector];

                    for (int z = 0; z < splitSize + 1; z++)
                    {
                        for (int x = 0; x < splitSize + 1; x++)
                        {
                            vertices[xs,zs][num] = new CustomVertex.PositionTextured(x - 0.5f + (xs * splitSize), 0, z - 0.5f + (zs * splitSize), 0, 0);

                            if (x % 2 == 0)
                            {
                                if (z % 2 == 0)
                                {
                                    vertices[xs,zs][num].Tu = 0;
                                    vertices[xs, zs][num].Tv = 1;
                                }
                                else
                                {
                                    vertices[xs, zs][num].Tu = 0;
                                    vertices[xs, zs][num].Tv = 0;
                                }
                            }
                            else
                            {
                                if (z % 2 == 0)
                                {
                                    vertices[xs, zs][num].Tu = 1;
                                    vertices[xs, zs][num].Tv = 1;
                                }
                                else
                                {
                                    vertices[xs, zs][num].Tu = 1;
                                    vertices[xs, zs][num].Tv = 0;
                                }
                            }

                            num++;
                        }

                        if (z != splitSize)
                        {
                            for (int x = 0; x < splitSize; x++)
                            {
                                vertices[xs, zs][num++] = new CustomVertex.PositionTextured(x + (xs * splitSize), 0, z + (zs * splitSize), 0.5f, 0.5f);
                            }
                        }

                    }
                    num = 0;
                }
            }

            BuildVertexBuffer();
        }

        private void BuildIndices()
        {
            ibs = new Dictionary<GameTexture, Dictionary<int, IndexBuffer>>[gridWidth / splitSize, gridLength / splitSize];
        }

        public void FinaliseIndexBuffers()
        {
            gridIndexBuffer = new IndexBuffer[gridWidth / splitSize, gridLength / splitSize];

            for (int z = 0; z < gridLength / splitSize; z++)
            {
                for (int x = 0; x < gridWidth / splitSize; x++)
                {
                    gridIndexBuffer[x, z] = new IndexBuffer(typeof(int), splitSize * splitSize * 12, Game.device, Usage.WriteOnly, Pool.Default);
                    gridIndexBuffer[x,z].SetData(sectorIndices, 0, LockFlags.None);
                }
            }           

            Game.device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
            Game.device.SamplerState[0].MaxAnisotropy = 16;

            Game.device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
            Game.device.SamplerState[0].MipFilter = TextureFilter.Anisotropic;
        }

        private void BuildMatrices()
        {
            rotMatrices = new Matrix[8];
            rotMatrices[0] = Matrix.RotationZ(Geometry.DegreeToRadian(0));
            rotMatrices[1] = Matrix.RotationZ(Geometry.DegreeToRadian(90));
            rotMatrices[2] = Matrix.RotationZ(Geometry.DegreeToRadian(180));
            rotMatrices[3] = Matrix.RotationZ(Geometry.DegreeToRadian(270));
            rotMatrices[4] = Matrix.RotationZ(Geometry.DegreeToRadian(0)) * Matrix.RotationX(Geometry.DegreeToRadian(180));
            rotMatrices[5] = Matrix.RotationZ(Geometry.DegreeToRadian(90)) * Matrix.RotationX(Geometry.DegreeToRadian(180));
            rotMatrices[6] = Matrix.RotationZ(Geometry.DegreeToRadian(180)) * Matrix.RotationX(Geometry.DegreeToRadian(180));
            rotMatrices[7] = Matrix.RotationZ(Geometry.DegreeToRadian(270)) * Matrix.RotationX(Geometry.DegreeToRadian(180));
        }

        private void BuildTextures()
        {
            textures = new GameTexture[gridWidth, gridLength];
            rotateTypes = new RotateType[gridWidth, gridLength];
        }

        #region Edit Land
        public void UpdateLand(List<CornerHeight> corners)
        {
            if (corners.Count > 0)
            {
                bool smallWrite = true;

                if (corners.Count > 10000) smallWrite = false;

                int x, z;
                int cen;
                int xs, zs;

                Dictionary<int, Dictionary<int, bool>> affected = new Dictionary<int, Dictionary<int, bool>>();

                bool onXedge = false;

                Map map = Game.gameWorld.Map;

                foreach (CornerHeight corner in corners)
                {
                    int vertexNum = 0;
                    
                    x = corner.X;
                    z = corner.Z;

                    int xW = x % splitSize;
                    int zW = z % splitSize;

                    xs = x / splitSize;
                    zs = z / splitSize;

                    vertexNum = 0;

                    if (xW != 0 && zW % splitSize != 0)
                    {
                        xW = x % splitSize;
                        zW = z % splitSize;

                        vertexNum = (zW * (splitSize + 1) + xW) + (zW * splitSize);
                        EditVertex(xs, zs, vertexNum, corner.Height, smallWrite);
                    }
                    else
                    {


                        if (x % splitSize == 0 && x != 0)
                        {
                            onXedge = true;

                            if (z != gridLength)
                            {

                                xW += splitSize;

                                vertexNum = (zW * (splitSize + 1) + xW) + (zW * splitSize);
                                EditVertex(xs - 1, zs, vertexNum, corner.Height, smallWrite);

                            }
                        }
                        if (z % splitSize == 0 && z != 0)
                        {
                            xW = x % splitSize;
                            zW += splitSize;

                            if (x != gridWidth)
                            {

                                vertexNum = (zW * (splitSize + 1) + xW) + (zW * splitSize);
                                EditVertex(xs, zs - 1, vertexNum, corner.Height, smallWrite);

                            }

                            if (onXedge)
                            {
                                xW += splitSize;

                                vertexNum = (zW * (splitSize + 1) + xW) + (zW * splitSize);
                                EditVertex(xs - 1, zs - 1, vertexNum, corner.Height, smallWrite);
                            }
                        }

                        if (x < gridWidth && z < gridLength)
                        {
                            xW = x % splitSize;
                            zW = z % splitSize;

                            vertexNum = (zW * (splitSize + 1) + xW) + (zW * splitSize);
                            EditVertex(xs, zs, vertexNum, corner.Height, smallWrite);
                        }
                    }

                    onXedge = false;

                    if (map.ValidateCoord(x, z))
                    {
                        if (!(affected.ContainsKey(x)))
                        {
                            affected.Add(x, new Dictionary<int, bool>());
                        }

                        affected[x][z] = true;
                    }

                    if (map.ValidateCoord(x - 1, z))
                    {
                        if (!(affected.ContainsKey(x - 1)))
                        {
                            affected.Add(x - 1, new Dictionary<int, bool>());
                        }
                        affected[x - 1][z] = true;
                    }

                    if (map.ValidateCoord(x, z - 1))
                    {
                        if (!(affected.ContainsKey(x)))
                        {
                            affected.Add(x, new Dictionary<int, bool>());
                        }
                        affected[x][z - 1] = true;
                    }

                    if (map.ValidateCoord(x - 1, z - 1))
                    {
                        if (!(affected.ContainsKey(x - 1)))
                        {
                            affected.Add(x - 1, new Dictionary<int, bool>());
                        }

                        affected[x - 1][z - 1] = true;
                    }
                }

                Stopwatch sw = new Stopwatch();
                sw.Start();

                bool[,] sectorsChanged = new bool[gridWidth / splitSize, gridLength / splitSize];

                foreach (int xCoord in affected.Keys)
                {
                    foreach (int zCoord in affected[xCoord].Keys)
                    {
                        x = xCoord % splitSize;
                        z = zCoord % splitSize;

                        xs = xCoord / splitSize;
                        zs = zCoord / splitSize;

                        sectorsChanged[xs, zs] = true;

                        cen = z * ((splitSize + 1) * 2 - 1) + splitSize + 1 + x;

                        float newHeight = map.GetTileHeight(xCoord, zCoord);

                        if (vertices[xs, zs][cen].Y != newHeight)
                        {
                            EditVertex(xs, zs, cen, newHeight, smallWrite); 
                        }
                    }
                }

                if (!smallWrite)
                {
                    sw.Stop();
                    sw.Start();

                    for (z = 0; z < gridWidth / splitSize; z++)
                    {
                        for (x = 0; x < gridLength / splitSize; x++)
                        {
                            if (sectorsChanged[x, z])
                            {
                                vertStreams[x, z] = vertexBuffers[x, z].Lock(0, 0, LockFlags.None);
                                vertStreams[x, z].Position = 0;
                                vertStreams[x, z].Write(vertices[x, z]);
                                vertStreams[x, z].Close();
                                vertexBuffers[x, z].Unlock();
                            }
                        }
                    }


                }
                else
                {
                    for (x = 0; x < gridWidth / splitSize; x++)
                    {
                        for (z = 0; z < gridLength / splitSize; z++)
                        {
                            if (vertStreams[x, z] != null)
                            {
                                //vertexBuffers[x, z].Unlock();
                                vertStreams[x, z].Close();
                            }  
                        }
                    }
                }

                sw.Stop();

                
            }
        }

        private void EditVertex(int xSector, int zSector, int vertNum, float height, bool doWrite)
        {
            vertices[xSector, zSector][vertNum].Y = height / Game.heightScale;

            if (doWrite)
            {

                GraphicsStream stream = vertStreams[xSector, zSector];

                if (stream == null)
                {
                    vertStreams[xSector, zSector] = vertexBuffers[xSector, zSector].Lock(0, 0, LockFlags.None);
                    stream = vertStreams[xSector, zSector];
                }
                else
                {
                    if (!stream.CanWrite)
                    {
                        stream = vertexBuffers[xSector, zSector].Lock(0, 0, LockFlags.None);
                    }
                }

                singleVert[0] = vertices[xSector, zSector][vertNum];

                stream.Position = vertNum * strideSize;
                stream.Write(singleVert);
            }
        }



        #endregion

        public void ToggleTileOn(int x, int z)
        {

        }

        public void ToggleTileOff(int x, int z)
        {
            int xs = x / splitSize;
            int zs = z / splitSize;

            int realX = x % splitSize;
            int realZ = z % splitSize;

            GraphicsStream indexStream = gridIndexBuffer[xs,zs].Lock(0, 0, LockFlags.None);
            int[] tris = new int[12];

            int startVert = realZ * splitSize + realX;

            indexStream.Position = startVert * (sizeof(int) * 12);
            indexStream.Write(tris);
            indexStream.Close();
            gridIndexBuffer[xs, zs].Unlock();
        }

        #region Add/Update Tile
        public void AddHeights(Tile[,] tiles)
        {
            Stopwatch sw2 = new Stopwatch();
            sw2.Start();

            sectorIndices = new int[splitSize * splitSize * 12];

            for (int z = 0; z < splitSize; z++)
            {
                for (int x = 0; x < splitSize; x++)
                {
                    int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
                    int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
                    int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
                    int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
                    int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

                    int indexNum = ((z % splitSize) * splitSize + (x % splitSize)) * 12;

                    sectorIndices[indexNum] = nw;
                    sectorIndices[indexNum + 1] = cen;
                    sectorIndices[indexNum + 2] = ne;

                    sectorIndices[indexNum + 3] = ne;
                    sectorIndices[indexNum + 4] = cen;
                    sectorIndices[indexNum + 5] = se;

                    sectorIndices[indexNum + 6] = se;
                    sectorIndices[indexNum + 7] = cen;
                    sectorIndices[indexNum + 8] = sw;

                    sectorIndices[indexNum + 9] = sw;
                    sectorIndices[indexNum + 10] = cen;
                    sectorIndices[indexNum + 11] = nw;
                }
            }

            sw2.Stop();

            FinaliseIndexBuffers();

        }

        public void UpdateHighlight(Tile tile, HighlightType type, RotateType rotType)
        {
            Coordinate tCoord = tile.Coordinate;

            int xs = tile.Coordinate.X / splitSize;
            int zs = tile.Coordinate.Z / splitSize;

            int x = tile.Coordinate.X % splitSize;
            int z = tile.Coordinate.Z % splitSize;

            int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
            int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
            int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
            int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
            int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

            int orientation = GetOrientation(x, z, (int)rotType);
            GameTexture tex = highlightToTex[type];

            if (highlightTypes[tCoord.X, tCoord.Z] != type || highlightRotates[tCoord.X, tCoord.Z] != rotType)
            {
                
                int start = 0;
                int oldStart = -1;
                int[] array = new int[12];

                if (tex != null)
                {

                    if (highlightIBs[xs, zs] == null)
                    {
                        highlightSpaces[xs, zs] = new Dictionary<GameTexture, Dictionary<int, List<int>>>();
                        highlightIndices[xs, zs] = new Dictionary<GameTexture, Dictionary<int, List<int>>>();
                        highlightIBs[xs, zs] = new Dictionary<GameTexture, Dictionary<int, IndexBuffer>>();
                    }

                    if (!(highlightIndices[xs, zs].ContainsKey(tex)))
                    {
                        highlightIndices[xs, zs][tex] = new Dictionary<int, List<int>>();
                        highlightIBs[xs, zs][tex] = new Dictionary<int, IndexBuffer>();
                    }

                    if (!(highlightIndices[xs, zs][tex].ContainsKey(orientation)))
                    {
                        highlightIndices[xs, zs][tex][orientation] = new List<int>();
                        highlightIBs[xs, zs][tex][orientation] = new IndexBuffer(typeof(int), allTilesIndices, Game.device, Usage.Dynamic, Pool.Default);
                    }

                    if (highlightSpaces[xs, zs].ContainsKey(tex) && highlightSpaces[xs, zs][tex].ContainsKey(orientation) && highlightSpaces[xs, zs][tex][orientation].Count > 0)
                    {
                        start = highlightSpaces[xs, zs][tex][orientation][0];
                        highlightSpaces[xs, zs][tex][orientation].RemoveAt(0);
                    }
                    else
                    {
                        start = highlightIndices[xs, zs][tex][orientation].Count;
                    }

                    if (highlightStarts.ContainsKey(tile))
                    {
                        oldStart = highlightStarts[tile];
                    }

                    array[0] = se;
                    array[1] = sw;
                    array[2] = cen;

                    array[3] = sw;
                    array[4] = nw;
                    array[5] = cen;

                    array[6] = nw;
                    array[7] = ne;
                    array[8] = cen;

                    array[9] = ne;
                    array[10] = se;
                    array[11] = cen;

                    if (start < highlightIndices[xs, zs][tex][orientation].Count)
                    {
                        for (int d = 0; d < 12; d++)
                        {
                            highlightIndices[xs, zs][tex][orientation][start + d] = array[d];
                        }
                    }
                    else
                    {
                        highlightIndices[xs, zs][tex][orientation].InsertRange(start, array);
                    }

                    GraphicsStream stm = highlightIBs[xs, zs][tex][orientation].Lock(0, 0, LockFlags.None);

                    stm.Position = start * 4;

                    stm.Write(array);

                    highlightIBs[xs, zs][tex][orientation].Unlock();

                }

                GameTexture oldTex = highlightToTex[highlightTypes[tCoord.X, tCoord.Z]];
                

                if (oldTex != null)
                {
                    array = new int[12];
                    int oldO = GetOrientation(tCoord.X, tCoord.Z, (int)highlightRotates[tCoord.X, tCoord.Z]);
                    oldStart = highlightStarts[tile];

                    //GAP IN TILEINDICES!
                    for (int i = 0; i < 12; i++)
                    {
                        highlightIndices[xs, zs][oldTex][oldO][oldStart + i] = 0;
                    }

                    if (!highlightSpaces[xs, zs].ContainsKey(oldTex))
                    {
                        highlightSpaces[xs, zs][oldTex] = new Dictionary<int, List<int>>();
                    }
                    if (!highlightSpaces[xs, zs][oldTex].ContainsKey(oldO))
                    {
                        highlightSpaces[xs, zs][oldTex][oldO] = new List<int>();
                    }

                    highlightSpaces[xs, zs][oldTex][oldO].Add(oldStart);

                    if (highlightIndices[xs, zs][oldTex][oldO][oldStart] != 0)
                    {

                    }

                    GraphicsStream stm2 = highlightIBs[xs, zs][oldTex][oldO].Lock(0, 0, LockFlags.None);
                    stm2.Position = oldStart * 4;
                    stm2.Write(array);

                    highlightIBs[xs, zs][oldTex][oldO].Unlock();

                }
                highlightStarts[tile] = start;
            }
            
            highlightTypes[tCoord.X, tCoord.Z] = type;
            highlightRotates[tCoord.X, tCoord.Z] = rotType;
        }

        public void UpdateTile(Tile tile)
        {
            Coordinate tCoord = tile.Coordinate;

            int xs = tile.Coordinate.X / splitSize;
            int zs = tile.Coordinate.Z / splitSize;

            int x = tile.Coordinate.X % splitSize;
            int z = tile.Coordinate.Z % splitSize;

            int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
            int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
            int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
            int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
            int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

            int orientation = GetOrientation(x, z, (int)tile.RotateType);
            GameTexture tex = tile.Texture;

            if (textures[tCoord.X, tCoord.Z] != tex || rotateTypes[tCoord.X, tCoord.Z] != tile.RotateType)
            {
                int start = 0;

                if (ibs[xs, zs] == null)
                {
                    indexSpaces[xs, zs] = new Dictionary<GameTexture, Dictionary<int, List<int>>>();
                    tileIndices[xs, zs] = new Dictionary<GameTexture, Dictionary<int, List<int>>>();
                    ibs[xs, zs] = new Dictionary<GameTexture, Dictionary<int, IndexBuffer>>();
                }

                if (!(tileIndices[xs,zs].ContainsKey(tex)))
                {
                    tileIndices[xs,zs][tex] = new Dictionary<int, List<int>>();
                    ibs[xs,zs][tex] = new Dictionary<int, IndexBuffer>();
                }

                if (!(tileIndices[xs, zs][tex].ContainsKey(orientation)))
                {
                    tileIndices[xs, zs][tex][orientation] = new List<int>();
                    ibs[xs, zs][tex][orientation] = new IndexBuffer(typeof(int), allTilesIndices, Game.device, Usage.Dynamic, Pool.Default);
                }

                if (indexSpaces[xs, zs].ContainsKey(tex) && indexSpaces[xs, zs][tex].ContainsKey(orientation) && indexSpaces[xs, zs][tex][orientation].Count > 0)
                {
                    start = indexSpaces[xs, zs][tex][orientation][0];
                    indexSpaces[xs, zs][tex][orientation].RemoveAt(0);
                }
                else
                {
                    start = tileIndices[xs, zs][tex][orientation].Count;
                }

                int oldStart = -1;

                if (indexStarts.ContainsKey(tile))
                {
                    oldStart = indexStarts[tile];
                }

                indexStarts[tile] = start;

                int[] array = new int[12];

                array[0] = se;
                array[1] = sw;
                array[2] = cen;

                array[3] = sw;
                array[4] = nw;
                array[5] = cen;

                array[6] = nw;
                array[7] = ne;
                array[8] = cen;

                array[9] = ne;
                array[10] = se;
                array[11] = cen;

                if (start < tileIndices[xs, zs][tex][orientation].Count)
                {
                    for (int d = 0; d < 12; d++)
                    {
                        tileIndices[xs, zs][tex][orientation][start + d] = array[d];
                    }
                }
                else
                {
                    tileIndices[xs, zs][tex][orientation].InsertRange(start, array);
                }

                GraphicsStream stm = ibs[xs, zs][tex][orientation].Lock(0, 0, LockFlags.None);

                stm.Position = start * 4;

                stm.Write(array);

                ibs[xs, zs][tex][orientation].Unlock();

                GameTexture oldTex = textures[tCoord.X, tCoord.Z];

                if (oldTex != null)
                {
                    array = new int[12];
                    int oldO = GetOrientation(tCoord.X, tCoord.Z, (int)rotateTypes[tCoord.X, tCoord.Z]);

                    //GAP IN TILEINDICES!
                    for (int i = 0; i < 12; i++)
                    {
                        tileIndices[xs, zs][oldTex][oldO][oldStart + i] = 0;
                    }

                    if (!indexSpaces[xs, zs].ContainsKey(oldTex))
                    {
                        indexSpaces[xs, zs][oldTex] = new Dictionary<int, List<int>>();
                    }
                    if (!indexSpaces[xs, zs][oldTex].ContainsKey(oldO))
                    {
                        indexSpaces[xs, zs][oldTex][oldO] = new List<int>();
                    }

                    indexSpaces[xs, zs][oldTex][oldO].Add(oldStart);

                    if (tileIndices[xs, zs][oldTex][oldO][oldStart] != 0)
                    {

                    }

                    GraphicsStream stm2 = ibs[xs, zs][oldTex][oldO].Lock(0, 0, LockFlags.None);
                    stm2.Position = oldStart * 4;
                    stm2.Write(array);

                    ibs[xs, zs][oldTex][oldO].Unlock();

                }
            }

            textures[tCoord.X, tCoord.Z] = tile.Texture;
            rotateTypes[tCoord.X, tCoord.Z] = tile.RotateType;

        } 
        #endregion

        public void UpdateSectors(AJCamera camera, List<Vector3> rays)
        {

        }

        #region Orientation Data
        public int GetOrientation(int x, int y, int rotnum)
        {
            if (x % 2 == 0)
            {
                if (y % 2 == 0)
                {
                    switch (rotnum)
                    {
                        case 0: return 0;
                        case 1: return 3;
                        case 2: return 2;
                        case 3: return 1;
                    }
                }
                else
                {
                    switch (rotnum)
                    {
                        case 0: return 4;
                        case 1: return 5;
                        case 2: return 6;
                        case 3: return 7;
                    }
                }
            }
            else
            {
                if (y % 2 == 0)
                {
                    switch (rotnum)
                    {
                        case 0: return 6;
                        case 1: return 7;
                        case 2: return 4;
                        case 3: return 5;
                    }
                }
                else
                {
                    switch (rotnum)
                    {
                        case 0: return 2;
                        case 1: return 1;
                        case 2: return 0;
                        case 3: return 3;
                    }
                }
            }
            return 0;
        } 
        #endregion

        #region Render
        public override void Render(AJCamera camera)
        {
            Game.device.Transform.World = Matrix.Identity;
            Game.device.Transform.View = camera.View;
            Game.device.Transform.Projection = camera.Projection;

            Game.device.VertexFormat = CustomVertex.PositionTextured.Format;
            Game.device.RenderState.CullMode = Cull.Clockwise;

            Game.device.SetTexture(0, grassTex.Texture);
            

            viewableLand.X = (((int)(camera.Position.X / splitSize) * splitSize) - (border * splitSize));
            viewableLand.Width = splitSize * (border * 2 + 1);
            viewableLand.Y = (((int)(camera.Position.Z / splitSize) * splitSize) - (border * splitSize));
            viewableLand.Height = splitSize * (border * 2 + 1);

            for (int zs = (int)(camera.Position.Z / splitSize) - border; zs < (int)(camera.Position.Z / splitSize) + border + 1; zs++)
            {
                for (int xs = (int)(camera.Position.X / splitSize) - border; xs < (int)(camera.Position.X / splitSize) + border + 1; xs++)
                {
                    if (xs >= 0 && zs >= 0 && xs < gridWidth / splitSize && zs < gridLength / splitSize)
                    {
                        Game.device.Indices = gridIndexBuffer[xs, zs];
                        Game.device.SetStreamSource(0, vertexBuffers[xs, zs], 0);
                        Game.device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices[xs, zs].Length, 0, splitSize * splitSize * 4);
                    }
                }
            }

            Game.device.RenderState.CullMode = Cull.CounterClockwise;

            Game.device.SetTextureStageState(0, TextureStageStates.TextureTransform, (int)TextureTransform.Count2);

            for (int zs = (int)(camera.Position.Z / splitSize) - border; zs < (int)(camera.Position.Z / splitSize) + border + 1; zs++)
            {
                for (int xs = (int)(camera.Position.X / splitSize) - border; xs < (int)(camera.Position.X / splitSize) + border + 1; xs++)
                {
                    if (xs >= 0 && zs >= 0 && xs < gridWidth / splitSize && zs < gridLength / splitSize)
                    {
                        if (ibs[xs, zs] != null)
                        {
                            foreach (GameTexture tex in ibs[xs, zs].Keys)
                            {
                                foreach (int o in ibs[xs, zs][tex].Keys)
                                {
                                    Game.device.Indices = ibs[xs, zs][tex][o];
                                    Game.device.SetTexture(0, tex.Texture);
                                    Game.device.SetStreamSource(0, vertexBuffers[xs, zs], 0);
                                    Game.device.Transform.Texture0 = rotMatrices[o];
                                    Game.device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, (tileIndices[xs, zs][tex][o].Count / 3));

                                }
                            }
                        }
                    }
                }

            }

            Game.device.RenderState.AlphaBlendEnable = true;
            Game.device.RenderState.SourceBlend = Blend.SourceAlpha;
            Game.device.RenderState.DestinationBlend = Blend.InvSourceAlpha;

            for (int zs = (int)(camera.Position.Z / splitSize) - border; zs < (int)(camera.Position.Z / splitSize) + border + 1; zs++)
            {
                for (int xs = (int)(camera.Position.X / splitSize) - border; xs < (int)(camera.Position.X / splitSize) + border + 1; xs++)
                {
                    if (xs >= 0 && zs >= 0 && xs < gridWidth / splitSize && zs < gridLength / splitSize)
                    {
                        if (highlightIBs[xs, zs] != null)
                        {
                            foreach (GameTexture tex in highlightIBs[xs, zs].Keys)
                            {
                                foreach (int o in highlightIBs[xs, zs][tex].Keys)
                                {
                                    Game.device.Indices = highlightIBs[xs, zs][tex][o];
                                    Game.device.SetTexture(0, tex.Texture);
                                    Game.device.SetStreamSource(0, vertexBuffers[xs, zs], 0);
                                    Game.device.Transform.Texture0 = rotMatrices[o];
                                    Game.device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, (highlightIndices[xs, zs][tex][o].Count / 3));

                                }
                            }
                        }
                    }
                }
            }

            Game.device.SetTextureStageState(0, TextureStageStates.TextureTransform, (int)TextureTransform.Disable);

        }

        #endregion

        #region Picking
        public GridPickData Pick(Vector3 rayStart, Vector3 rayDirection, AJCamera camera)
        {
            Matrix inverseWorld = Matrix.Identity;
            inverseWorld.Invert();

            Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld);
            Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld);
            localDirection.Normalize();

            Vector3 zipMan = new Vector3(localStart.X, localStart.Y, localStart.Z);
            IntersectInformation intInf;

            float fraction = 4;

            bool wentIntoGrid = false;
            bool leftGrid = false;

            Coordinate cameraTile = GetCoordFor(camera.Position.X, camera.Position.Z);

            if (cameraTile.X != -1) { wentIntoGrid = true; }

            Dictionary<Coordinate, bool> possibles = new Dictionary<Coordinate, bool>();

            while (!(wentIntoGrid && leftGrid) && zipMan.Y > (Math.Abs(localDirection.Y) * -1) && zipMan.Y < 2000)
            {
                localDirection.Multiply(1.0f / fraction);
                zipMan += localDirection;

                Coordinate overTile = GetCoordFor(zipMan.X, zipMan.Z);

                if (overTile.X != -1)
                {
                    if (zipMan.Y < (MaxHeight(overTile) + Math.Abs(localDirection.Y)) && zipMan.Y > (MinHeight(overTile) - Math.Abs(localDirection.Y)))
                    {

                        List<Coordinate> neighbours = Coordinate.GetNeighbours(overTile);

                        foreach (Coordinate c in neighbours)
                            possibles[c] = true;

                        possibles[overTile] = true;
                    }

                    if (!wentIntoGrid) { wentIntoGrid = true; }
                }
                else
                {
                    if (wentIntoGrid) { leftGrid = true; }
                }

                localDirection.Multiply(fraction);
            }

            int ne, se, sw, nw, cen;
            Vector3 vectorSE, vectorSW, vectorNW, vectorNE, vectorCentre;

            Coordinate nearestTile = new Coordinate(-1, -1);
            IntersectInformation nearestIntInf = new IntersectInformation();

            nearestIntInf.Dist = float.MaxValue;

            int xs = -1;
            int zs = -1;

            foreach (Coordinate c in possibles.Keys)
            {
                xs = c.X / splitSize;
                zs = c.Z / splitSize;

                int x = c.X % splitSize;
                int z = c.Z % splitSize;

                se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
                sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
                nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
                ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
                cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

                vectorSE = new Vector3(vertices[xs, zs][se].X, vertices[xs, zs][se].Y, vertices[xs, zs][se].Z);
                vectorSW = new Vector3(vertices[xs, zs][sw].X, vertices[xs, zs][sw].Y, vertices[xs, zs][sw].Z);
                vectorNW = new Vector3(vertices[xs, zs][nw].X, vertices[xs, zs][nw].Y, vertices[xs, zs][nw].Z);
                vectorNE = new Vector3(vertices[xs, zs][ne].X, vertices[xs, zs][ne].Y, vertices[xs, zs][ne].Z);
                vectorCentre = new Vector3(vertices[xs, zs][cen].X, vertices[xs, zs][cen].Y, vertices[xs, zs][cen].Z);

                IntersectInformation intInfN;
                IntersectInformation intInfE;
                IntersectInformation intInfS;
                IntersectInformation intInfW;

                bool checkN = Geometry.IntersectTri(vectorNW, vectorCentre, vectorNE, localStart, localDirection, out intInfN);
                bool checkE = Geometry.IntersectTri(vectorNE, vectorCentre, vectorSE, localStart, localDirection, out intInfE);
                bool checkS = Geometry.IntersectTri(vectorSE, vectorCentre, vectorSW, localStart, localDirection, out intInfS);
                bool checkW = Geometry.IntersectTri(vectorSW, vectorCentre, vectorNW, localStart, localDirection, out intInfW);

                if ((checkN && intInfN.Dist < nearestIntInf.Dist)) { nearestTile = c; nearestIntInf = intInfN; }
                if ((checkE && intInfE.Dist < nearestIntInf.Dist)) { nearestTile = c; nearestIntInf = intInfE; }
                if ((checkS && intInfS.Dist < nearestIntInf.Dist)) { nearestTile = c; nearestIntInf = intInfS; }
                if ((checkW && intInfW.Dist < nearestIntInf.Dist)) { nearestTile = c; nearestIntInf = intInfW; }

            }

            Coordinate vertex = Coordinate.Invalid;
            RotateType side = RotateType.Invalid;

            if (!nearestTile.IsInvalid)
            {
                vertex = PickCorner(nearestTile, rayStart, rayDirection, camera);
                side = PickSide(nearestTile, rayStart, rayDirection, camera);
            }

            return new GridPickData(nearestTile, vertex, side);
        }

        private Coordinate PickCorner(Coordinate tile, Vector3 rayStart, Vector3 rayDirection, AJCamera camera)
        {
            IntersectInformation interInfo;
            Pick(rayStart, rayDirection, camera, out interInfo);

            if (!tile.IsInvalid)
            {
                #region Matrixy Stuff
                Matrix World =
                        Matrix.Scaling(myScaling) *
                        Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) *
                        Matrix.Translation(myPosition);

                Matrix inverseWorld = Matrix.Invert(World);

                Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld);
                Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld);
                localDirection.Normalize();
                #endregion

                int xs = tile.X / splitSize;
                int zs = tile.Z / splitSize;

                int x = tile.X % splitSize;
                int z = tile.Z % splitSize;

                int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
                int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
                int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
                int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
                int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

                Vector3 vectorSE = new Vector3(vertices[xs, zs][se].X, vertices[xs, zs][se].Y, vertices[xs, zs][se].Z);
                Vector3 vectorSW = new Vector3(vertices[xs, zs][sw].X, vertices[xs, zs][sw].Y, vertices[xs, zs][sw].Z);
                Vector3 vectorNW = new Vector3(vertices[xs, zs][nw].X, vertices[xs, zs][nw].Y, vertices[xs, zs][nw].Z);
                Vector3 vectorNE = new Vector3(vertices[xs, zs][ne].X, vertices[xs, zs][ne].Y, vertices[xs, zs][ne].Z);
                Vector3 vectorCentre = new Vector3(vertices[xs, zs][cen].X, vertices[xs, zs][cen].Y, vertices[xs, zs][cen].Z);

                List<Vector3> compassPoints = new List<Vector3>();

                compassPoints.Add(new Vector3(tile.X, AveHeight(vertices[xs, zs][nw], vertices[xs, zs][ne]), tile.Z + 0.5f)); // North
                compassPoints.Add(vectorNE); // NorthEast
                compassPoints.Add(new Vector3(tile.X + 0.5f, AveHeight(vertices[xs, zs][ne], vertices[xs, zs][se]), tile.Z)); //East
                compassPoints.Add(vectorSE); // SouthEast
                compassPoints.Add(new Vector3(tile.X, AveHeight(vertices[xs, zs][se], vertices[xs, zs][sw]), tile.Z - 0.5f)); // South
                compassPoints.Add(vectorSW); // SouthWest
                compassPoints.Add(new Vector3(tile.X - 0.5f, AveHeight(vertices[xs, zs][nw], vertices[xs, zs][sw]), tile.Z)); // West
                compassPoints.Add(vectorNW); // NorthWest
                compassPoints.Add(compassPoints[0]); // North again to complete the circle;

                int numTriangle = -1;


                IntersectInformation intInf;

                for (int i = 0; i < compassPoints.Count - 1; i++)
                {
                    bool check = Geometry.IntersectTri(compassPoints[i], vectorCentre, compassPoints[i + 1], localStart, localDirection, out intInf);

                    if (check)
                    {
                        numTriangle = i;
                        break;

                    }
                }

                if (numTriangle < 2) { return new Coordinate(tile.X + 1, tile.Z + 1); }
                else if (numTriangle < 4) { return new Coordinate(tile.X + 1, tile.Z); }
                else if (numTriangle < 6) { return new Coordinate(tile.X, tile.Z); }
                else if (numTriangle < 8) { return new Coordinate(tile.X, tile.Z + 1); }
            }

            return Coordinate.Invalid;

        }

        public RotateType PickSide(Coordinate tile, Vector3 rayStart, Vector3 rayDirection, AJCamera camera)
        {
            IntersectInformation interInfo;
            Pick(rayStart, rayDirection, camera, out interInfo);

            if (!tile.IsInvalid)
            {
                #region Matrixy Stuff
                Matrix World =
                        Matrix.Scaling(myScaling) *
                        Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) *
                        Matrix.Translation(myPosition);

                Matrix inverseWorld = Matrix.Invert(World);

                Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld);
                Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld);
                localDirection.Normalize();
                #endregion

                int xs = tile.X / splitSize;
                int zs = tile.Z / splitSize;

                int x = tile.X % splitSize;
                int z = tile.Z % splitSize;

                int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
                int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
                int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
                int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
                int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

                Vector3 neV = new Vector3(tile.X + 0.5f, vertices[xs, zs][ne].Y, tile.Z + 0.5f);
                Vector3 seV = new Vector3(tile.X + 0.5f, vertices[xs, zs][nw].Y, tile.Z - 0.5f);
                Vector3 swV = new Vector3(tile.X - 0.5f, vertices[xs, zs][sw].Y, tile.Z - 0.5f);
                Vector3 nwV = new Vector3(tile.X - 0.5f, vertices[xs, zs][nw].Y, tile.Z + 0.5f);
                Vector3 cenV = new Vector3(tile.X, vertices[xs, zs][cen].Y, tile.Z);

                List<Vector3> sides = new List<Vector3>();
                sides.Add(nwV); //NW
                sides.Add(neV); //NE
                sides.Add(seV); //SE
                sides.Add(swV); //SW
                sides.Add(nwV); //NW

                int numTriangle = -1;

                IntersectInformation intInf;

                for (int i = 0; i < sides.Count - 1; i++)
                {
                    bool check = Geometry.IntersectTri(sides[i], cenV, sides[i + 1], localStart, localDirection, out intInf);

                    if (check)
                    {
                        numTriangle = i;
                        break;
                    }
                }

                if (numTriangle == 0) { return RotateType.Zero; }
                else if (numTriangle == 1) { return RotateType.Ninety; }
                else if (numTriangle == 2) { return RotateType.OneEighty; }
                else if (numTriangle == 3) { return RotateType.TwoSeventy; }
            }

            return RotateType.Invalid;
        }

        #endregion

        #region Utility Methods
        private Coordinate GetCoordFor(float x, float z)
        {
            Coordinate tile = new Coordinate(-1, -1);

            if (x > -0.5 && z > -0.5 && x < gridWidth - 0.5 && z < gridLength - 0.5)
            {
                tile.X = (int)Math.Floor(x + 0.5f);
                tile.Z = (int)Math.Floor(z + 0.5f);
            }

            return tile;
        }

        private float AveHeight(CustomVertex.PositionTextured a, CustomVertex.PositionTextured b)
        {
            return (a.Y + b.Y) / 2;
        }

        private float MaxHeight(Coordinate coord)
        {
            int xs = coord.X / splitSize;
            int zs = coord.Z / splitSize;

            int x = coord.X % splitSize;
            int z = coord.Z % splitSize;

            int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
            int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
            int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
            int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
            int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

            return Math.Max(Math.Max(vertices[xs, zs][se].Y, vertices[xs, zs][sw].Y), Math.Max(vertices[xs, zs][nw].Y, vertices[xs, zs][ne].Y));
        }

        private float MinHeight(Coordinate coord)
        {
            int xs = coord.X / splitSize;
            int zs = coord.Z / splitSize;

            int x = coord.X % splitSize;
            int z = coord.Z % splitSize;

            int se = ((z * (((splitSize + 1) * 2) - 1)) + x + 1);
            int sw = ((z * (((splitSize + 1) * 2) - 1)) + x);
            int nw = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2) - 1);
            int ne = ((z * (((splitSize + 1) * 2) - 1)) + x + ((splitSize + 1) * 2));
            int cen = ((z * (((splitSize + 1) * 2) - 1)) + (splitSize + 1) + x);

            return Math.Min(Math.Min(vertices[xs, zs][se].Y, vertices[xs, zs][sw].Y), Math.Min(vertices[xs, zs][nw].Y, vertices[xs, zs][ne].Y));
        } 
        #endregion

        #region Resource Handling
        public override void ReloadResources()
        {
            BuildVertexBuffer();
            FinaliseIndexBuffers();
            ReBuildTileIndexBuffers();
        }

        private void ReBuildTileIndexBuffers()
        {
            //GameTexture[] texKeys = new GameTexture[ibs.Keys.Count];
            //ibs.Keys.CopyTo(texKeys, 0);
            //int[] orientations;
            //foreach (GameTexture tex in texKeys)
            //{
            //    orientations = new int[ibs[tex].Keys.Count];
            //    ibs[tex].Keys.CopyTo(orientations, 0);

            //    foreach (int orientation in orientations)
            //    {
            //        ibs[tex][orientation] = new IndexBuffer(typeof(int), allTilesIndices, Game.device, Usage.Dynamic, Pool.Default);
            //        ibs[tex][orientation].SetData(tileIndices[tex][orientation].ToArray(), 0, LockFlags.None);
            //    }
            //}
        }

        private void BuildVertexBuffer()
        {
            vertexBuffers = new VertexBuffer[gridWidth / splitSize, gridLength / splitSize];
            int numberOfVerts = ((splitSize + 1) * (splitSize + 1)) + (splitSize * splitSize);

            for (int x = 0; x < gridWidth / splitSize; x++)
            {
                for (int z = 0; z < gridLength / splitSize; z++)
                {
                    vertexBuffers[x, z] = new VertexBuffer(typeof(CustomVertex.PositionTextured), numberOfVerts, Game.device, Usage.WriteOnly | Usage.Dynamic,
                                                            CustomVertex.PositionTextured.Format, Pool.Default);

                    vertexBuffers[x, z].SetData(vertices[x, z], 0, LockFlags.None);
                }
            }
        } 
        #endregion
    }

    public struct GridPickData
    {
        private Coordinate coordinate;
        private Coordinate vertex;
        private RotateType side;

        public GridPickData(Coordinate coordinate, Coordinate vertex, RotateType side)
        {
            this.coordinate = coordinate;
            this.vertex = vertex;
            this.side = side;
        }

        public Coordinate Coord
        {
            get { return coordinate; }
        }

        public Coordinate CornerVertex
        {
            get { return vertex; }
        }

        public RotateType TileSide
        {
            get { return side; }
        }

    }

    public enum HighlightType { NoHighlight, Half, Full }

}